package com.shihan.sqlSession;

import com.shihan.config.BoundSql;
import com.shihan.pojo.Configuration;
import com.shihan.pojo.MappedStatement;
import com.shihan.utils.GenericTokenParser;
import com.shihan.utils.ParameterMapping;
import com.shihan.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class SimpleExecutor implements Executor {

    /**
     * 具体的 JDBC 操作代码
     * 因为底层最终还是使用的是 JDBC
     * @param configuration
     * @param mappedStatement
     * @param params
     * @param <E>
     * @return
     */
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        //1、从 configuration 对象中，拿到一个 connection 连接
        Connection connection = configuration.getDataSource().getConnection();

        //2、从 mappedStatement 中，获取到配置文件中的SQL语句
        //   PS：select * from user where id=#{id} and username=#{username}
        String sql = mappedStatement.getSql();

        //3、解析配置文件中的 SQL 语句，将参数位置替换成 ?
        //   PS：并且保存配置文件中 SQL 描述的参数及其位置，方便后续给 ? 进行参数填充
        //   PS：#{属性名} ，所以可以按顺序保存 属性名，后续可以使用反射进行参数值的填充
        //说明：BoundSql 用来封装替换了参数占位符的 SQL，以及 #{} 中的属性名集合列表
        BoundSql boundSql = getBoundSql(sql);

        //4、创建 PreparedStatement 对象
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        //5、填充参数值
        //   获取到参数类型的 Class 对象，方便反射获取 属性值
        //   PS：参数类型的全类名，以字符串的形式，保存在 mappedStatement 对象中；
        String parameterType = mappedStatement.getParameterType();
        //   PS：得到参数类型的 Class 对象，方便进行反射获取属性值
        Class<?> parameterTypeClass = parseToClass(parameterType);

        //   获取到 #{} 中属性名封装到的对象集合
        List<ParameterMapping> parameterMappings = boundSql.getParameterMapping();
        //   遍历属性名封装对象的集合，进行参数赋值
        for (int i = 0; i < parameterMappings.size(); i++) {
            // 属性名
            String content = parameterMappings.get(i).getContent();

            // 通过反射，获取到对象中某些属性的值
            // PS：要先获取到 Filed 对象
            Field declaredField = parameterTypeClass.getDeclaredField(content);
            // PS：为防止属性为 private ，暴力设置为可访问的
            declaredField.setAccessible(true);

            // PS：此时只需要传入相关对象，即可获取到该对象中的属性值；此处的 param[0] <==> user 对象
            // PS：此处的 o 就是实际的参数值
            Object o = declaredField.get(params[0]);

            // 进行参数填充
            preparedStatement.setObject(i+1,o);

        }

        //6、执行 SQL
        ResultSet resultSet = preparedStatement.executeQuery();
        // PS：获取到返回值的类型、Class对象，方便使用反射创建对象，并给属性 set 数据
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = parseToClass(resultType);

        // PS：用来接收封装的结果集数据对象的集合
        ArrayList<Object> objects = new ArrayList<>();


        //7、遍历处理 ResultSet 结果集，并封装数据对象
        while (resultSet.next()){
            //  PS：在每次保存到 List 集合之前，都要新 new 一个对象；
            Object o = resultTypeClass.newInstance();
            // 获取到 所有数据列 的信息对象 -- 元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount() ; i++) {
                // index 索引 从 1 开始，先获取到数据列的列名信息；（PS：列名信息 和 对象的属性名信息，是一一对应的）
                String columnName = metaData.getColumnName(i);
                // 通过 内省，获取到 JavaBean 某个属性的 set 方法
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                // 通过 内省，调用执行 属性的set方法，给对象的某个属性赋值；（PS：对象是 o，值来源于 resultSet 中的列数据）
                writeMethod.invoke(o,resultSet.getObject(columnName));
            }

            objects.add(o);

        }

        return (List<E>) objects;
    }


    /**
     * 增、删、改 操作，底层统一调用此方法
     * @param configuration
     * @param mappedStatement
     * @param params
     * @return
     */
    public int update(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        // 1、先从 DataSource 中获取到一个 Connection
        Connection connection = configuration.getDataSource().getConnection();

        // 2、拿到 #{} 占位符的 SQL 语句
        // insert into user values(#{id},#{username})
        String sql = mappedStatement.getSql();
        // 解析上述 sql ,生成 ? 占位符的 sql，并解析占位符参数的属性名集合
        BoundSql boundSql = getBoundSql(sql);

        // 获取得到 ? 占位符的 sql
        String sqlText = boundSql.getSqlText();
        // 获取得到 #{} 中占位符参数的属性名集合
        List<ParameterMapping> parameterMappings = boundSql.getParameterMapping();

        // 3、拿到 PreparedStatement 对象，并进行参数填充
        PreparedStatement preparedStatement = connection.prepareStatement(sqlText);

        // 拿到 paramType 指明的类型，将要通过反射从中取值，并放到 PreparedStatement 中；
        String parameterType = mappedStatement.getParameterType();
        Class<?> aClass = parseToClass(parameterType);

        // 给 PreparedStatement 的参数进行赋值
        if(parameterMappings!=null && parameterMappings.size()>0){
            for(int i=0;i<parameterMappings.size();i++){

                ParameterMapping parameterMapping = parameterMappings.get(i);

                // 获取得到属性名
                String content = parameterMapping.getContent();

                // 获取得到 Field 对象
                Field declaredField = aClass.getDeclaredField(content);
                // 暴力设置可以获取到属性；
                declaredField.setAccessible(true);

                Object o = declaredField.get(params[0]);

                preparedStatement.setObject(i+1,o);

            }

        }

        // 4、执行 PreparedStatement ，获得结果，并返回结果即可，不需要对返回数据进行封装
        int i = preparedStatement.executeUpdate();

        return i;
    }

    private Class<?> parseToClass(String parameterType) throws ClassNotFoundException {
        if(parameterType!=null){
            Class<?> aClass = Class.forName(parameterType);
            return aClass;
        }

        return null;
    }

    /**
     * 说明：此方法中，主要完成以下两个功能（需要借助工具类来辅助实现）
     * 1、替换 #{} 占位符为 ?
     * 2、解析 #{} 中的属性名，并进行存储
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        // 说明：一个 #{} 中的属性名，将会被封装到一个 ParameterMapping 对象中
        // 说明：所以 SQL 中所有的 #{} 内容，使用 List<ParameterMapping> 存储即可
        // 说明：而ParameterMappingTokenHandler 就是用来存储 List<ParameterMapping> 的
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        // GenericTokenParser 构造方法中，指明相关属性：自定义占位符的起始标志、结束标志，存储占位符属性的集合对象
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        // 调用 parse 方法解析 SQL，将解析后的 SQL 语句，以及 #{} 中属性的集合，封装到 BoundSql 中；
        // 这里获得的 SQL 是 ? 作为参数占位符的 SQL
        String sqlText = genericTokenParser.parse(sql);

        // 从 parameterMappingTokenHandler 中获取 #{} 属性名集合
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        // 将这两个数据，封装到 BoundSql 中
        BoundSql boundSql = new BoundSql(sqlText, parameterMappings);

        return boundSql;
    }


}
